import sys
from utils.console.colors import Colors
from utils.misc.functional import cacheable
from utils.misc.timing import *

class Log:
    class Levels:
        error = 'ERROR'
        fatal = 'FATAL'
        warn = 'WARN'
        info = 'INFO'
        debug = 'DEBUG'

    permanentLevels = (
        Levels.fatal,
        Levels.error
    )

    logFormats = {
        Levels.error: Colors.magenta,
        Levels.fatal: Colors.red,
        Levels.warn: Colors.yellow,
        Levels.info: Colors.green,
        Levels.debug: Colors.gray
    }

    @cacheable
    class ConsoleWriter:
        def __init__(self, **kwargs):
            self.formatter = Log.fullFormat

        def __repr__(self):
            return 'Console Writer'

        def write(self, application, logLevel, description, data):
            print Log.fullFormat(application, logLevel, description, data)

    class FileWriter:
        class RotateTimers:
            afternoon = timeToAfternoon
            midnight = timeToMidnight

        LOG_DIRECTORY_FORMAT = 'logs/{application}'
        LOG_FILE_NAME_FORMAT = '{date}_{time}.log'
        # all file writers will rotate at afternoon by default
        rotateTimer = RotateTimers.afternoon

        def __init__(self, **kwargs):
            try:
                self.formatter = Log.plainFormat
                self.path = kwargs['path']
                self.needRotate = kwargs['needRotate']
                self.logFile = None
            except KeyError:
                raise ValueError('Not enough parameters: [path, needRotate, timeRotate]')
            except IOError:
                raise ValueError('Path doesnt exists: {0}'.format(self.path))

        def __repr__(self):
            return 'File Writer'

        def write(self, application, logLevel, description, data):
            pass

    class SocketWriter:
        def __init__(self, **kwargs):
            self.formatter = Log.plainFormat
            raise NotImplemented

        def __repr__(self):
            return 'Socket Writer'

        def write(self, application, logLevel, description, data):
            pass

    class WritersTypes:
        console = 'CONSOLE'
        file = 'FILE'
        socket = 'SOCKET'

    __enabledWriters = {
        WritersTypes.console: ConsoleWriter,
        WritersTypes.file: FileWriter
    }

    @cacheable
    def addWriter(self, type=WritersTypes.console, **kwargs):
        with self.lock:
            try:
                writer = self.__enabledWriters[type](**kwargs)
                writer.id = self.__writersCounter
                self.writers.setdefault(self.__writersCounter, writer)
                self.__writersCounter += 1
                return writer
            except KeyError:
                self.error('Writer type: {0}. This type of writer doesnt supported.'.format(
                    type
                ))
                return None
            except ValueError:
                try:
                    del self.writers[self.__writersCounter]
                except KeyError:
                    pass
            return None

    def removeWriter(self, type, **kwargs):
        with self.lock:
            writer = self.addWriter(type, **kwargs)
            if writer is not None:
                try:
                    del self.writers[writer.id]
                except KeyError:
                    pass

    def __init__(self, application):
        import threading
        self.disabledLevels = set()
        self.writers = {} # {name -> writer}
        self.application = str(application)
        self.__writersCounter = 0
        self.lock = threading.Lock()
        self.addWriter()

    @classmethod
    def fullFormat(cls, application, logLevel, description, data):
        sec = time.time()
        return '{time}\t{level}\t{description}\t{data}'.format(
            time=time.strftime('%d/%m %H:%M:%S', time.localtime(sec)),
            level = '{color}{level}{default}'.format(
                color=cls.logFormats.get(logLevel, ''),
                level=logLevel,
                default = Colors.default
            ),
            description='[ {bold}{appName}{default} ]'.format(
                bold=Colors.bold,
                appName=application,
                default=Colors.default
            ) + (''.join(
                ('[ {description} ]'.format(description=descr) for descr in description)
            )) if isinstance(description, list) else '[ Unknown ]',
            data=data
        )

    @classmethod
    def plainFormat(cls, application, logLevel, description, data):
        sec = time.time()
        return '{time}\t{level}\t{description}\t{data}'.format(
            time=time.strftime('%d/%m %H:%M:%S', time.localtime(sec)),
            level = logLevel,
            description='[ {appName} ]'.format(
                appName=application
            ) + (''.join(
                ('[ {description} ]'.format(description=descr) for descr in description)
            )) if isinstance(description, list) else '[ Unknown ]',
            data=data
        )

    #args: Log.Levels or 'FATAL', 'ERROR', etc.
    def enableLevels(self, *args):
        with self.lock:
            for logLevel in args:
                try:
                    self.disabledLevels.remove(logLevel)
                except KeyError:
                    pass

    #args: Log.Levels or 'WARN', 'DEBUG', etc.
    def disableLevels(self, *args):
        with self.lock:
            for logLevel in args:
                if logLevel in self.permanentLevels:
                    continue
                self.disabledLevels.add(logLevel)

    def __toWriter(self, logLevel, description, data):
        with self.lock:
            if logLevel in self.disabledLevels:
                return
            for writer in self.writers.itervalues():
                try:
                    writer.write(
                        application=self.application,
                        logLevel=logLevel,
                        description=description,
                        data=data
                    )
                except NotImplemented:
                    pass
                except AttributeError:
                    #should never happened
                    sys.exit('ERROR: Application: {0}. Writer: {1}. Not defined and not implemented'.format(
                        self.application,
                        writer
                    ))

    #logString = u'', description = []
    def error(self, logString, description=[]):
        self.__toWriter(self.Levels.error, description, logString)

    #logString = u'', description = []
    def fatal(self, logString, description=[]):
        self.__toWriter(self.Levels.fatal, description, logString)

    #logString = u'', description = []
    def warn(self, logString, description=[]):
        self.__toWriter(self.Levels.warn, description, logString)

    #logString = u'', description = []
    def info(self, logString, description=[]):
        self.__toWriter(self.Levels.info, description, logString)

    #logString = u'', description = []
    def debug(self, logString, description=[]):
        self.__toWriter(self.Levels.debug, description, logString)
